DMMF: 依存関係の注入
LT;DR
すべての依存関係をトップレベルの関数に渡して、バケツリレー する 本書はこれ
コンポジションルート は エントリポイントと近いほうが良い
依存関係が多い場合、その原因のほとんどは関数の責務が多すぎることである
対応策
小さく分割する
上記が出来ない場合は、低レベルの関数をトップレベルの関数の外側でセットし、すべての依存関係が組み込まれている関数を渡す ようにすれば良い
このように、ある関数が別の関数に渡されるときの インタフェース(関数の型)は、すべての依存関係を隠し、できるだけ小さくすべきである hr.icon
関数型で『依存性の注入』を実現する
トップレベルから 依存関係 を必要とする関数まで、どのように依存関係を渡すか 実現方法
依存関係が暗黙的になるのは好ましくないので、依存関係を明示的なパラメータとして渡す
もっとも単純な方法
すべての依存関係をトップレベルの関数に渡して、バケツリレー する code:fsharp
let placeOrder
checkProductCodeExists
checkAddressExists
getProductPrice
createOrderAcknowledgmentLetter
sendOrderAcknowledgment
: PlaceOrderWorkflow =
fun unvalidatedOrder ->
// ...
各ステップ
code:fsharp
let validateOrder: ValidateOrder =
fun checkProductCodeExists
checkAddressExists
unvalidatedOrder ->
let shippingAddress =
unvalidatedOrder.ShippingAddress
|> toAddress checkAddressExists
// ...
let lines =
unvalidatedOrder.Lines
|> List.map (toValidatedOrderLine checkProductCodeExists)
ヘルパ関数
code:fsharp
let toValidatedOrderLine checkProductExists unvalidatedOrderLine =
let orderLineId = ...
let productCode =
unvalidatedOrderLine.ProductCode
|> toProductCode checkProductExists
e.g. Suave を用いた Web アプリケーション code:fsharp
let app: WebPart =
// ワークフローで使用するサービスの設定
let checkProductCodeExists = ...
let checkAddressExists = ...
let getProductPrice = ...
let createOrderAcknowledgmentLetter = ...
let sendOrderAcknowledgment = ...
let toHttpResponse = ...
// サービスの部分適用による placeOrder ワークフローの設定
let placeOrder =
placeOrder
checkProductCodeExists
checkAddressExists
getProductPrice
createOrderAcknowledgmentLetter
sendOrderAcknowledgment
// 他のワークフローの設定
let changeOrder = ...
let cancelOrder = ...
// ルーティングの設定
choose
[ POST >=> choose
[ path "/placeOrder"
=> deserializeOrder // JSON を未検証の注文に変換
=> placeOrder // ワークフローを実行
=> postEvents // イベントをキューに投入
=> toHttpResponse // 出力に基づきレスポンスを返す
path "/changeOrder"
=> ...
path "/cancelOrder"
=> ...
]
]
依存関係が多すぎる場合にすべきこと
依存関係 が多すぎる場合、関数が行っていることが多すぎる可能性がある 小さい要素に分割できないか?
難しい場合は、依存関係を 1 つのレコード構造にまとめる
1 つのパラメータとして渡すことも考えられる
よくあるのが、子関数の依存関係が複雑になっているケース
e.g. checkAddressExists 関数が URI エンドポイントと認証情報を必要とする Web サービスと通信している
code:fsharp
let checkAddressExists endPoint crendentials =
....
toAddress の呼び出しにも、これらの 2 つのパラメータを渡す必要がある?
code:fsharp
let toAddress checkAddressExists endPoint crendentials unvalidatedAddress =
...
let checkAddress = checkAddressExists endPoint crendentials unvalidatedAddress
toAddress の呼び出し元も、その呼び出し元も渡す必要が出てくる
code:fsharp
let validateOrder
checkProductCodeExists
checkAddressExists
endPoint
credentials
unvalidatedOrder =
...
...
代替案
低レベルの関数をトップレベルの関数の外側でセットし、すべての依存関係が組み込まれている関数を渡す
code:fsharp
let placeOrder: PlaceOrderWorkflow =
let endPoint = ...
let credentials = ...
// 認証情報を埋め込んだ checkAddressExists の新しいバージョンを作成
let checkAddressExists = checkAddressExists endPoint credentials
// ワークフローのステップを設定
let validateOrder =
validateOrder checkProductCodeExists checkAddressExists
...
// ワークフロー関数を返す
fun unvalidatedOrder ->
unvalidatedOrder
|> validateOrder
// ...
ある関数が別の関数に渡されるときの インタフェース(関数の型)は、すべての依存関係を隠し、できるだけ小さくすべきである